#include <stdio.h>
#include <string.h>
#include <rpm/rpmcli.h>

#define RPM_VERSION(major,minor) (major*1000+minor)

#include <rpm/rpmts.h>
#include <rpm/rpmte.h>
#include <rpm/rpmdb.h>

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

/* Chip, this is somewhat stripped down from the default callback used by
   the rpmcli.  It has to be here to insure that we open the pkg again. 
   If we don't do this we get segfaults.  I also, kept the updating of some
   of the rpmcli static vars, but I may not have needed to do this.

   Also, we probably want to give a nice interface such that we could allow
   users of Rpmlib to do their own callback, but that will have to come later.
*/
void * _null_callback(
	const void * arg, 
	const rpmCallbackType what,
	const rpm_loff_t amount,
	const rpm_loff_t total,
	fnpyKey key, 
	rpmCallbackData data)
{
	Header h = (Header) arg;
	int flags = (int) ((long)data);
	void * rc = NULL;
	const char * filename = (const char *)key;
	static FD_t fd = NULL;
	int xx;

	/* Code stolen from rpminstall.c and modified */
	switch(what) {
		case RPMCALLBACK_INST_OPEN_FILE:
	 		if (filename == NULL || filename[0] == '\0')
			     return NULL;
			fd = Fopen(filename, "r.ufdio");
			/* FIX: still necessary? */
			if (fd == NULL || Ferror(fd)) {
				fprintf(stderr, "open of %s failed!\n", filename);
				if (fd != NULL) {
					xx = Fclose(fd);
					fd = NULL;
				}
			} else
#if Rpmlib_API < RPM_VERSION(4,9)
				fd = fdLink(fd, "persist (showProgress)");
#else
				fd = fdLink(fd);
#endif
			return (void *)fd;
	 		break;

	case RPMCALLBACK_INST_CLOSE_FILE:
		/* FIX: still necessary? */
#if Rpmlib_API < RPM_VERSION(4,9)
				fd = fdFree(fd, "persist (showProgress)");
#else
				fd = fdFree(fd);
#endif
		if (fd != NULL) {
			xx = Fclose(fd);
			fd = NULL;
		}
		break;

	case RPMCALLBACK_INST_START:
		rpmcliHashesCurrent = 0;
		if (h == NULL || !(flags & INSTALL_LABEL))
			break;
		break;

	case RPMCALLBACK_TRANS_PROGRESS:
	case RPMCALLBACK_INST_PROGRESS:
		break;

	case RPMCALLBACK_TRANS_START:
		rpmcliHashesCurrent = 0;
		rpmcliProgressTotal = 1;
		rpmcliProgressCurrent = 0;
		break;

	case RPMCALLBACK_TRANS_STOP:
		rpmcliProgressTotal = rpmcliPackagesTotal;
		rpmcliProgressCurrent = 0;
		break;

	case RPMCALLBACK_REPACKAGE_START:
		rpmcliHashesCurrent = 0;
		rpmcliProgressTotal = total;
		rpmcliProgressCurrent = 0;
		break;

	case RPMCALLBACK_REPACKAGE_PROGRESS:
		break;

	case RPMCALLBACK_REPACKAGE_STOP:
		rpmcliProgressTotal = total;
		rpmcliProgressCurrent = total;
		rpmcliProgressTotal = rpmcliPackagesTotal;
		rpmcliProgressCurrent = 0;
		break;

	case RPMCALLBACK_UNINST_PROGRESS:
		break;
	case RPMCALLBACK_UNINST_START:
		break;
	case RPMCALLBACK_UNINST_STOP:
		break;
	case RPMCALLBACK_UNPACK_ERROR:
		break;
	case RPMCALLBACK_CPIO_ERROR:
		break;
	case RPMCALLBACK_UNKNOWN:
		break;
	default:
		break;
	}
	
	return rc;	
}

void
_populate_header_tags(HV *href)
{
    rpmtd names;
    const char *name;

    names = rpmtdNew();
    rpmTagGetNames(names, 1);
    while ((name = rpmtdNextString(names)) != NULL) {
        (void)hv_store(href, name, strlen(name),
            newSViv(rpmTagGetValue(name + strlen("RPMTAG_"))), 0);
    }
}

void
_populate_constant(HV *href, char *name, int val)
{
    (void)hv_store(href, name, strlen(name), newSViv(val), 0);
}

#define REGISTER_CONSTANT(name) _populate_constant(constants, #name, name)

MODULE = Rpmlib PACKAGE = Rpmlib

PROTOTYPES: ENABLE
BOOT:
    {
	HV *header_tags, *constants;
	rpmReadConfigFiles(NULL, NULL);

	header_tags = perl_get_hv("Rpmlib::header_tag_map", TRUE);
	_populate_header_tags(header_tags);

	constants = perl_get_hv("Rpmlib::constants", TRUE);

	/* not the 'standard' way of doing perl constants, but a lot easier to maintain */
	REGISTER_CONSTANT(RPMVSF_DEFAULT);
	REGISTER_CONSTANT(RPMVSF_NOHDRCHK);
	REGISTER_CONSTANT(RPMVSF_NEEDPAYLOAD);
	REGISTER_CONSTANT(RPMVSF_NOSHA1HEADER);
	REGISTER_CONSTANT(RPMVSF_NOMD5HEADER);
	REGISTER_CONSTANT(RPMVSF_NODSAHEADER);
	REGISTER_CONSTANT(RPMVSF_NORSAHEADER);
	REGISTER_CONSTANT(RPMVSF_NOSHA1);
	REGISTER_CONSTANT(RPMVSF_NOMD5);
	REGISTER_CONSTANT(RPMVSF_NODSA);
	REGISTER_CONSTANT(RPMVSF_NORSA);
	REGISTER_CONSTANT(_RPMVSF_NODIGESTS);
	REGISTER_CONSTANT(_RPMVSF_NOSIGNATURES);
	REGISTER_CONSTANT(_RPMVSF_NOHEADER);
	REGISTER_CONSTANT(_RPMVSF_NOPAYLOAD);
	REGISTER_CONSTANT(TR_ADDED);
	REGISTER_CONSTANT(TR_REMOVED);
    }

double
rpm_api_version(pkg)
	char * pkg
    CODE:
	(void)pkg; /* Not used */
	int major = Rpmlib_API / 1000;
	double minor = (int)Rpmlib_API % 1000;
	while (minor >= 1) { minor /= 10; }
	RETVAL = major + minor;
    OUTPUT:
	RETVAL

int
rpmvercmp(one, two)
	char* one
	char* two

void
_read_package_info(fp, vsflags)
	FILE *fp
	int vsflags
    PREINIT:
	rpmts ts;
	Header ret;
	rpmRC rc;
	FD_t fd;
    PPCODE:
	ts = rpmtsCreate();

        /* XXX Determine type of signature verification when reading
	vsflags |= _RPMTS_VSF_NOLEGACY;
	vsflags |= _RPMTS_VSF_NODIGESTS;
	vsflags |= _RPMTS_VSF_NOSIGNATURES;
	xx = rpmtsSetVerifySigFlags(ts, vsflags);
        */ 

	fd = fdDup(fileno(fp));
	rpmtsSetVSFlags(ts, vsflags);
	rc = rpmReadPackageFile(ts, fd, "filename or other identifier", &ret);

	Fclose(fd);

	if (rc == RPMRC_OK) {
	    SV *h_sv;

	    EXTEND(SP, 1);

	    h_sv = sv_newmortal();
            sv_setref_pv(h_sv, "Rpmlib::C::Header", (void *)ret);

	    PUSHs(h_sv);
	}
	else {
	    croak("error reading package");
	}
	ts = rpmtsFree(ts);

void
_read_from_file(fp)
	FILE *fp
PREINIT:
	SV *h_sv;
	FD_t fd;
	Header h;
PPCODE:
	fd = fdDup(fileno(fp));
	h = headerRead(fd, HEADER_MAGIC_YES);

	if (h) {
	    EXTEND(SP, 1);

	    h_sv = sv_newmortal();
	    sv_setref_pv(h_sv, "Rpmlib::C::Header", (void *)h);

	    PUSHs(h_sv);
	}
	Fclose(fd);


MODULE = Rpmlib		PACKAGE = Rpmlib::C::PackageIterator
Header
_iterator_next(i)
	rpmdbMatchIterator i
    PREINIT:
	Header       ret;
        SV *         h_sv;
	unsigned int offset;
    PPCODE:
	ret = rpmdbNextIterator(i);
	if (ret)
		headerLink(ret);
	if(ret != NULL) 
		offset = rpmdbGetIteratorOffset(i);
	else
		offset = 0;
	
	EXTEND(SP, 2);
	h_sv = sv_newmortal();
	sv_setref_pv(h_sv, "Rpmlib::C::Header", (void *)ret);
	PUSHs(h_sv);
	PUSHs(sv_2mortal(newSViv(offset)));
        RETVAL = ret;



MODULE = Rpmlib		PACKAGE = Rpmlib::C::Header

void
DESTROY(h)
	Header h
    CODE:
	headerFree(h);

void
tag_by_id(h, tag)
	Header h
	int tag
    PREINIT:
	rpmtd tagdata;
	int ok;
    PPCODE:
	tagdata = rpmtdNew();
	if (tagdata == NULL) {
		croak("Out of memory");
	}
	ok = headerGet(h, tag, tagdata, HEADERGET_DEFAULT);

	if (!ok) {
		/* nop, empty stack */
	}
	else {
		switch(tagdata->type)
		{
		case RPM_STRING_ARRAY_TYPE:
			{
			int i;
			char **s;

			EXTEND(SP, tagdata->count);
			s = (char **)tagdata->data;

			for (i = 0; i < tagdata->count; i++) {
				PUSHs(sv_2mortal(newSVpv(s[i], 0)));
			}
			}
			break;
		case RPM_STRING_TYPE:
			PUSHs(sv_2mortal(newSVpv((char *)tagdata->data, 0)));
			break;
		case RPM_CHAR_TYPE:
			{
			int i;
			char *r;

			EXTEND(SP, tagdata->count);
			r = (char *)tagdata->data;

			for (i = 0; i < tagdata->count; i++) {
				PUSHs(sv_2mortal(newSViv(r[i])));
			}
			}
			break;
		case RPM_INT8_TYPE:
			{
			int i;
			uint8_t *r;

			EXTEND(SP, tagdata->count);
			r = (uint8_t *)tagdata->data;

			for (i = 0; i < tagdata->count; i++) {
				PUSHs(sv_2mortal(newSViv(r[i])));
			}
			}
			break;
		case RPM_INT16_TYPE:
			{
			int i;
			uint16_t *r;

			EXTEND(SP, tagdata->count);
			r = (uint16_t *)tagdata->data;

			for (i = 0; i < tagdata->count; i++) {
				PUSHs(sv_2mortal(newSViv(r[i])));
			}
			}
			break;
		case RPM_INT32_TYPE:
			{
			int i;
			uint32_t *r;

			EXTEND(SP, tagdata->count);
			r = (uint32_t *)tagdata->data;

			for (i = 0; i < tagdata->count; i++) {
				PUSHs(sv_2mortal(newSViv(r[i])));
			}
			}
			break;
		default:
			croak("unknown rpm tag type %d", tagdata->type);
		}
	}
	rpmtdFreeData(tagdata);

int
_header_compare(h1, h2)
	Header h1
	Header h2
    CODE:
	RETVAL = rpmVersionCompare(h1, h2);
    OUTPUT:
        RETVAL

int
_header_is_source(h)
	Header h
    CODE:
	RETVAL = headerIsEntry(h, RPMTAG_SOURCEPACKAGE);
    OUTPUT:
	RETVAL

void
_header_sprintf(h, format)
	Header h
	char * format
    PREINIT:
	char * s;
    PPCODE:
	s =  headerFormat(h, format, NULL);
	PUSHs(sv_2mortal(newSVpv((char *)s, 0)));
/* By the way, the #if below is completely useless, free() would work for both */
	free(s);


